/*----------------------------------------------------------------------/
/ Low level disk I/O module function checker
/-----------------------------------------------------------------------/
/ WARNING: The data on the target drive will be lost!
*/

#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "diskio.h"


static
DWORD pn(
    DWORD pns
)
{
    static DWORD lfsr;
    UINT n;

    if(pns)
    {
        lfsr = pns;

        for(n = 0; n < 32; n++)
        {
            pn(0);
        }
    }

    if(lfsr & 1)
    {
        lfsr >>= 1;
        lfsr ^= 0x80200003;
    }
    else
    {
        lfsr >>= 1;
    }

    return lfsr;
}


int test_diskio(
    BYTE pdrv,      /* Physical drive number to be checked (all data on the drive will be lost) */
    UINT ncyc,      /* Number of test cycles */
    DWORD *buff,    /* Pointer to the working buffer */
    UINT sz_buff    /* Size of the working buffer in unit of byte */
)
{
    UINT n, cc, ns;
    DWORD sz_drv, lba, lba2, pns = 1;
    WORD sz_sect, sz_eblk;
    BYTE *pbuff = (BYTE *)buff;
    DSTATUS ds;
    DRESULT dr;
    printf("test_diskio(%u, %u, 0x%08X, 0x%08X)\n", pdrv, ncyc, (UINT)buff, sz_buff);

    if(sz_buff < _MAX_SS + 4)
    {
        printf("Insufficient work area to test.\n");
        return 1;
    }

    for(cc = 1; cc <= ncyc; cc++)
    {
        printf("**** Test cycle %u of %u start ****\n", cc, ncyc);
        /* Initialization */
        printf(" disk_initalize(%u)", pdrv);
        ds = disk_initialize(pdrv);

        if(ds & STA_NOINIT)
        {
            printf(" - failed.\n");
            return 2;
        }
        else
        {
            printf(" - ok.\n");
        }

        /* Get drive size */
        printf("**** Get drive size ****\n");
        printf(" disk_ioctl(%u, GET_SECTOR_COUNT, 0x%08X)", pdrv, (UINT)&sz_drv);
        sz_drv = 0;
        dr = disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_drv);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 3;
        }

        if(sz_drv < 128)
        {
            printf("Failed: Insufficient drive size to test.\n");
            return 4;
        }

        printf(" Number of sectors on the drive %u is %lu.\n", pdrv, sz_drv);
#if _MAX_SS != _MIN_SS
        /* Get sector size */
        printf("**** Get sector size ****\n");
        printf(" disk_ioctl(%u, GET_SECTOR_SIZE, 0x%X)", pdrv, (UINT)&sz_sect);
        sz_sect = 0;
        dr = disk_ioctl(pdrv, GET_SECTOR_SIZE, &sz_sect);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 5;
        }

        printf(" Size of sector is %u bytes.\n", sz_sect);
#else
        sz_sect = _MAX_SS;
#endif
        /* Get erase block size */
        printf("**** Get block size ****\n");
        printf(" disk_ioctl(%u, GET_BLOCK_SIZE, 0x%X)", pdrv, (UINT)&sz_eblk);
        sz_eblk = 0;
        dr = disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_eblk);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
        }

        if(dr == RES_OK || sz_eblk >= 2)
        {
            printf(" Size of the erase block is %u sectors.\n", sz_eblk);
        }
        else
        {
            printf(" Size of the erase block is unknown.\n");
        }

        /* Single sector write test */
        printf("**** Single sector write test 1 ****\n");
        lba = 0;

        for(n = 0, pn(pns); n < sz_sect; n++)
        {
            pbuff[n] = (BYTE)pn(0);
        }

        printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba);
        dr = disk_write(pdrv, pbuff, lba, 1);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 6;
        }

        printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv);
        dr = disk_ioctl(pdrv, CTRL_SYNC, 0);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 7;
        }

        memset(pbuff, 0, sz_sect);
        printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba);
        dr = disk_read(pdrv, pbuff, lba, 1);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 8;
        }

        for(n = 0, pn(pns); n < sz_sect && pbuff[n] == (BYTE)pn(0); n++) ;

        if(n == sz_sect)
        {
            printf(" Data matched.\n");
        }
        else
        {
            printf("Failed: Read data differs from the data written.\n");
            return 10;
        }

        pns++;
        /* Multiple sector write test */
        printf("**** Multiple sector write test ****\n");
        lba = 1;
        ns = sz_buff / sz_sect;

        if(ns > 4)
        {
            ns = 4;
        }

        for(n = 0, pn(pns); n < (UINT)(sz_sect * ns); n++)
        {
            pbuff[n] = (BYTE)pn(0);
        }

        printf(" disk_write(%u, 0x%X, %lu, %u)", pdrv, (UINT)pbuff, lba, ns);
        dr = disk_write(pdrv, pbuff, lba, ns);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 11;
        }

        printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv);
        dr = disk_ioctl(pdrv, CTRL_SYNC, 0);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 12;
        }

        memset(pbuff, 0, sz_sect * ns);
        printf(" disk_read(%u, 0x%X, %lu, %u)", pdrv, (UINT)pbuff, lba, ns);
        dr = disk_read(pdrv, pbuff, lba, ns);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 13;
        }

        for(n = 0, pn(pns); n < (UINT)(sz_sect * ns) && pbuff[n] == (BYTE)pn(0); n++) ;

        if(n == (UINT)(sz_sect * ns))
        {
            printf(" Data matched.\n");
        }
        else
        {
            printf("Failed: Read data differs from the data written.\n");
            return 14;
        }

        pns++;
        /* Single sector write test (misaligned memory address) */
        printf("**** Single sector write test 2 ****\n");
        lba = 5;

        for(n = 0, pn(pns); n < sz_sect; n++)
        {
            pbuff[n + 3] = (BYTE)pn(0);
        }

        printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff + 3), lba);
        dr = disk_write(pdrv, pbuff + 3, lba, 1);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 15;
        }

        printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv);
        dr = disk_ioctl(pdrv, CTRL_SYNC, 0);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 16;
        }

        memset(pbuff + 5, 0, sz_sect);
        printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff + 5), lba);
        dr = disk_read(pdrv, pbuff + 5, lba, 1);

        if(dr == RES_OK)
        {
            printf(" - ok.\n");
        }
        else
        {
            printf(" - failed.\n");
            return 17;
        }

        for(n = 0, pn(pns); n < sz_sect && pbuff[n + 5] == (BYTE)pn(0); n++) ;

        if(n == sz_sect)
        {
            printf(" Data matched.\n");
        }
        else
        {
            printf("Failed: Read data differs from the data written.\n");
            return 18;
        }

        pns++;
        /* 4GB barrier test */
        printf("**** 4GB barrier test ****\n");

        if(sz_drv >= 128 + 0x80000000 / (sz_sect / 2))
        {
            lba = 6;
            lba2 = lba + 0x80000000 / (sz_sect / 2);

            for(n = 0, pn(pns); n < (UINT)(sz_sect * 2); n++)
            {
                pbuff[n] = (BYTE)pn(0);
            }

            printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba);
            dr = disk_write(pdrv, pbuff, lba, 1);

            if(dr == RES_OK)
            {
                printf(" - ok.\n");
            }
            else
            {
                printf(" - failed.\n");
                return 19;
            }

            printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff + sz_sect), lba2);
            dr = disk_write(pdrv, pbuff + sz_sect, lba2, 1);

            if(dr == RES_OK)
            {
                printf(" - ok.\n");
            }
            else
            {
                printf(" - failed.\n");
                return 20;
            }

            printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv);
            dr = disk_ioctl(pdrv, CTRL_SYNC, 0);

            if(dr == RES_OK)
            {
                printf(" - ok.\n");
            }
            else
            {
                printf(" - failed.\n");
                return 21;
            }

            memset(pbuff, 0, sz_sect * 2);
            printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba);
            dr = disk_read(pdrv, pbuff, lba, 1);

            if(dr == RES_OK)
            {
                printf(" - ok.\n");
            }
            else
            {
                printf(" - failed.\n");
                return 22;
            }

            printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff + sz_sect), lba2);
            dr = disk_read(pdrv, pbuff + sz_sect, lba2, 1);

            if(dr == RES_OK)
            {
                printf(" - ok.\n");
            }
            else
            {
                printf(" - failed.\n");
                return 23;
            }

            for(n = 0, pn(pns); pbuff[n] == (BYTE)pn(0) && n < (UINT)(sz_sect * 2); n++) ;

            if(n == (UINT)(sz_sect * 2))
            {
                printf(" Data matched.\n");
            }
            else
            {
                printf("Failed: Read data differs from the data written.\n");
                return 24;
            }
        }
        else
        {
            printf(" Test skipped.\n");
        }

        pns++;
        printf("**** Test cycle %u of %u completed ****\n\n", cc, ncyc);
    }

    return 0;
}



int main(int argc, char *argv[])
{
    int rc;
    DWORD buff[512];  /* 2048 byte working buffer */
    /* Check function/compatibility of the physical drive #0 */
    rc = test_diskio(0, 1, buff, sizeof buff);

    if(res)
    {
        printf("Sorry the function/compatibility test failed.\nFatFs will not work on this disk driver.\n");
    }
    else
    {
        printf("Congratulations! The disk I/O layer works well.\n");
    }

    return rc;
}

